// LocationQueryValue 类型，表示查询参数的值，可以为字符串或 null
export type LocationQueryValue = string | null;

/**
 * 标准化的查询对象类型，出现在 {@link RouteLocationNormalized} 中 键为字符串，值为 LocationQueryValue 或 LocationQueryValue 数组
 *
 * @public
 */
export type LocationQuery = Record<string, LocationQueryValue | LocationQueryValue[]>;

// LocationQueryValueRaw 类型，扩展了 LocationQueryValue，支持 number 和 undefined
export type LocationQueryValueRaw = LocationQueryValue | number | undefined;

// LocationQueryRaw 类型，支持键为 string 或 number，值为 LocationQueryValueRaw 或其数组
export type LocationQueryRaw = Record<
	string | number,
	LocationQueryValueRaw | LocationQueryValueRaw[]
>;

/**
 * 将查询字符串解析为 LocationQuery 对象，兼容带或不带 ? 前缀的字符串 功能类似于 URLSearchParams
 *
 * @param search 查询字符串
 * @returns 解析后的查询对象
 */
export const PLUS_RE = /\+/g; // 匹配 + 号（%2B）

/** 匹配 = 号（%3D）用于编码时替换 */
const EQUAL_RE = /[=]/g;
const ENC_BRACKET_OPEN_RE = /%5B/g; // 匹配 [
const ENC_BRACKET_CLOSE_RE = /%5D/g; // 匹配 ]
const ENC_CARET_RE = /%5E/g; // 匹配 ^
const ENC_BACKTICK_RE = /%60/g; // 匹配 `
const ENC_CURLY_OPEN_RE = /%7B/g; // 匹配 {
const ENC_PIPE_RE = /%7C/g; // 匹配 |
const ENC_CURLY_CLOSE_RE = /%7D/g; // 匹配 }
/** 匹配 URL 编码的空格（%20）用于解码 */
const ENC_SPACE_RE = /%20/g;
const HASH_RE = /#/g; // 匹配 #（%23）
/** 匹配 & 号（%26）用于编码时替换 */
const AMPERSAND_RE = /&/g;

/**
 * 解析查询字符串为对象
 *
 * @param search 查询字符串（可带或不带 ?）
 * @returns LocationQuery 对象
 */
/**
 * 解析查询字符串为对象
 *
 * @example
 *   parseQuery('page=1&sort=desc'); // { page: '1', sort: 'desc' }
 *   parseQuery('?filter=active'); // { filter: 'active' }
 *
 * @param search 查询字符串（可带或不带 ?）
 * @returns 解析后的查询对象
 */
export function parseQuery(search: string): LocationQuery {
	const query: LocationQuery = {};

	// 避免因 split('&') 产生空 key 和空 value
	if (search === '' || search === '?') return query;

	// 使用 startsWith 判断是否以 '?' 开头
	const hasLeadingIM = search.startsWith('?');
	const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&');

	for (const searchParam of searchParams) {
		// 先将 + 号解码为空格
		const decodedParam = searchParam.replace(PLUS_RE, ' ');
		// 允许 = 字符
		const eqPos = decodedParam.indexOf('=');
		const key = decode(eqPos < 0 ? decodedParam : decodedParam.slice(0, eqPos));
		const value = eqPos < 0 ? null : decode(decodedParam.slice(eqPos + 1));

		if (key in query) {
			// 处理同名参数，合并为数组
			let currentValue = query[key];
			if (!Array.isArray(currentValue)) {
				currentValue = [currentValue];
				query[key] = currentValue;
			}
			(currentValue as LocationQueryValue[]).push(value);
		} else {
			query[key] = value;
		}
	}

	return query;
}

/**
 * 将 LocationQueryRaw 对象序列化为查询字符串
 *
 * @param query 查询对象
 * @returns 查询字符串（不带 ?）
 */
/**
 * 将 LocationQueryRaw 对象序列化为查询字符串
 *
 * @example
 *   stringifyQuery({ page: 1, limit: 10 }); // 'page=1&limit=10'
 *   stringifyQuery({ filter: null }); // 'filter'
 *
 * @param query 查询对象
 * @returns 查询字符串（不带 ?）
 */
export function stringifyQuery(query: LocationQueryRaw): string {
	let search = '';

	for (const [originalKey, value] of Object.entries(query)) {
		const key = encodeQueryKey(originalKey);
		if (value === null) {
			// 仅当值为 null 时添加 key
			if (value !== undefined) {
				search += (search.length ? '&' : '') + key;
			}
		}
		// 保留 null 值
		const values: LocationQueryValueRaw[] = Array.isArray(value)
			? value.map(v => v && encodeQueryValue(v))
			: [value && encodeQueryValue(value)];

		for (const v of values) {
			// 跳过数组中的 undefined
			if (v !== undefined) {
				search += (search.length ? '&' : '') + key;
				if (v !== null) search += `=${v}`;
			}
		}
	}

	return search;
}

/**
 * 解码查询参数，防止解码失败时返回原值
 *
 * @param text 待解码字符串或数字
 * @returns 解码后的字符串
 */
/**
 * 解码查询参数，防止解码失败时返回原值
 *
 * @example
 *   decode('Hello%20World'); // 'Hello World'
 *   decode('%3D'); // '=')
 *
 * @param text 待解码字符串或数字
 * @returns 解码后的字符串
 */
export function decode(text: string | number): string {
	try {
		return decodeURIComponent(`${text}`);
	} catch {
		console.warn(`Error decoding "${text}". Using original value`);
	}
	return `${text}`;
}

/**
 * 编码查询参数的 key，确保 = 被正确转义
 *
 * @param text 待编码的 key
 * @returns 编码后的 key 字符串
 */
export function encodeQueryKey(text: string | number): string {
	return encodeQueryValue(text).replace(EQUAL_RE, '%3D');
}

/**
 * 编码查询参数的 value，处理特殊字符
 *
 * @param text 待编码的 value
 * @returns 编码后的 value 字符串
 */
/**
 * 编码查询参数的 value，处理特殊字符
 *
 * 1. 先进行通用编码
 * 2. 特殊字符替换顺序:
 *
 *    - 空格 → +
 *    - - → %2B
 *    - # → %23
 *    - & → %26
 *    - `→` (还原)
 *    - { → {
 *    - } → }
 *    - ^ → ^
 *
 * @param text 待编码的 value
 * @returns 编码后的 value 字符串
 */
export function encodeQueryValue(text: string | number): string {
	return (
		commonEncode(text)
			// 空格编码为 +，+ 编码为 %2B
			.replace(PLUS_RE, '%2B')
			.replace(ENC_SPACE_RE, '+')
			.replace(HASH_RE, '%23')
			.replace(AMPERSAND_RE, '%26')
			.replace(ENC_BACKTICK_RE, '`')
			.replace(ENC_CURLY_OPEN_RE, '{')
			.replace(ENC_CURLY_CLOSE_RE, '}')
			.replace(ENC_CARET_RE, '^')
	);
}

/**
 * 通用编码函数，处理部分特殊字符
 *
 * @param text 待编码字符串或数字
 * @returns 编码后的字符串
 */
function commonEncode(text: string | number): string {
	return encodeURI(`${text}`)
		.replace(ENC_PIPE_RE, '|')
		.replace(ENC_BRACKET_OPEN_RE, '[')
		.replace(ENC_BRACKET_CLOSE_RE, ']');
}
